#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <fcntl.h>
#include "queue.h"

#define PORT        8000
#define BUF_LEN     1024
#define MAX_CONN    100

#define blocking(s)     fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK)
#define nonblocking(s)  fcntl(s, F_SETFL, fcntl(s, F_GETFL) |  O_NONBLOCK)

void eventloop();
void process_events();
void handle_io_events();
void handle_pending_events();

/* callbacks */
void read_callback(struct event * event);
void write_callback(struct event * event);
void accept_callback(struct event * event);
void close_callback(struct event * event);

struct context {
    int status;
    char * txbuf;
    char * rxbuf;
};

static queue_node queue;
static char buffer[BUF_LEN];
// 在有大量非socket fd的情况下索引用fd不合适, 比如connfd=3000
// 这种情况下用连接数作为索引合适. 不过这里为了简单可以就这样做
static struct context * ctx[MAX_CONN];
static int listenfd;
static int maxfd;
fd_set readfds;
fd_set writefds;
fd_set exceptfds;

int main(int argc, char * argv[])
{
    struct sockaddr_in addr_in;
    int addr_len;

    addr_len = sizeof(addr_in);
    int opt = 1;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    maxfd = listenfd > maxfd ? listenfd : maxfd;
    printf("listenfd=%d\n", listenfd);
    struct context listen_ctx = {
        .status = S_LISTENING,
    };
    ctx[listenfd] = &listen_ctx;

    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT,
            &opt, sizeof(opt))) {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }

    // nonblocking(listenfd);

    addr_in.sin_family = AF_INET;
    addr_in.sin_addr.s_addr = INADDR_ANY;
    addr_in.sin_port = htons(PORT);

    if (bind(listenfd, (struct sockaddr *)&addr_in, sizeof(addr_in))) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    if (listen(listenfd, MAX_CONN) == -1) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    /////////////////////////////////////////////

    eventloop();

    return 0;
}

void eventloop()
{
    queue_init(&queue);

    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_ZERO(&exceptfds);

    while (1) {
        process_events();
    }

    close(listenfd);
}

void process_events()
{
    // process IO events
    handle_io_events();

    // process all pending events at the end of a cycle
    handle_pending_events();
}

void handle_io_events()
{
    // use select() to implement IO multiplexing

    for (int fd = maxfd; fd >= 0; fd--) {
        if (ctx[fd]) {
            FD_SET(fd, &readfds);
        }
    }

    int nevents = 0;
    int nfds = maxfd;
    //int nselect = select(nfds + 1, &readfds, &writefds, &exceptfds, NULL);
    int nselect = select(nfds + 1, &readfds, &writefds, NULL, NULL);
    //printf("select return: %d\n", nselect);
    for (int fd = 0; fd <= nfds; fd++) {
        // EV_READ | EV_ACCEPT
        if (FD_ISSET(fd, &readfds)) {
            printf("fd = %d is readable\n", fd);
            // add to pending queue
            struct event * event = malloc(sizeof(struct event));
            if (ctx[fd]->status & S_LISTENING) {
                event->type = EV_ACCEPT;
                event->callback = accept_callback;
            } else if (ctx[fd]->status & S_READING){
                event->type = EV_READ;
                event->callback = read_callback;
            }
            event->fd = fd;
            event->nbytes = BUF_LEN;
            enqueue(&queue, event);
            if (!(ctx[fd]->status & S_RX_ALLOC)) {
                ctx[fd]->rxbuf = malloc(BUF_LEN);
                ctx[fd]->status |= S_RX_ALLOC;
            }
            nevents++;
        }
        // EV_WRITE
        if (FD_ISSET(fd, &writefds)) {
            printf("fd = %d is writable\n", fd);
            // add to pending queue
            struct event * event = malloc(sizeof(struct event));
            event->callback = write_callback;
            event->fd = fd;
            enqueue(&queue, event);
            if (!(ctx[fd]->status & S_TX_ALLOC)) {
                ctx[fd]->txbuf = malloc(BUF_LEN);
                ctx[fd]->status |= S_TX_ALLOC;
            }
            nevents++;
        }

        if (nevents == nselect) {
            break;
        }
    }
}

void read_callback(struct event * event)
{
    printf("read_callback\n");
    int nread = read(event->fd, ctx[event->fd]->rxbuf, event->nbytes);
    if (nread == -1) {
        perror("read failed");
        // add a EV_CLOSE event to pending queue
        struct event * new_event = malloc(sizeof(struct event));
        new_event->type = EV_CLOSE;
        new_event->callback = close_callback;
        new_event->fd = event->fd;
        new_event->nbytes = BUF_LEN;
        enqueue(&queue, new_event);
        ctx[event->fd]->status = S_CLOSING;
    } else if (nread == 0) {
        printf("read end.\n");
        // add a EV_CLOSE event to pending queue
        struct event * new_event = malloc(sizeof(struct event));
        new_event->type = EV_CLOSE;
        new_event->callback = close_callback;
        new_event->fd = event->fd;
        new_event->nbytes = BUF_LEN;
        enqueue(&queue, new_event);
        ctx[event->fd]->status = S_CLOSING;
    } else {
        printf("read %ld bytes: %s\n", strlen(ctx[event->fd]->rxbuf), ctx[event->fd]->rxbuf);
        FD_SET(event->fd, &writefds);
    }
}

void write_callback(struct event * event)
{
    printf("write_callback\n");
    // copy rxbuf data to txbuf
    strcpy(ctx[event->fd]->txbuf, ctx[event->fd]->rxbuf);
    // then, clearnup rxbuf
    memset(ctx[event->fd]->rxbuf, 0, BUF_LEN);
    int nwrite = write(event->fd, ctx[event->fd]->txbuf, strlen(ctx[event->fd]->txbuf));
    FD_CLR(event->fd, &writefds);
    if (nwrite == -1) {
        perror("write failed");
        // add a EV_CLOSE event to pending queue
        struct event * new_event = malloc(sizeof(struct event));
        new_event->type = EV_CLOSE;
        new_event->callback = close_callback;
        new_event->fd = event->fd;
        new_event->nbytes = BUF_LEN;
        enqueue(&queue, new_event);
        ctx[event->fd]->status = S_CLOSING;
    } else {
        printf("write %ld bytes: %s\n", strlen(ctx[event->fd]->txbuf), ctx[event->fd]->txbuf);
    }
}

void accept_callback(struct event * event)
{
    printf("accept_callback\n");
    struct sockaddr_in addr_in;
    int addr_len;

    addr_len = sizeof(addr_in);

    int connfd = accept(listenfd, (struct sockaddr *)&addr_in,
            (socklen_t *)&addr_len);
    if (connfd == -1) {
        perror("accept failed");
    } else {
        printf("connfd=%d\n", connfd);
        ctx[connfd] = malloc(sizeof(struct context));
        // ctx[connfd]->status = S_READING | S_WRITING;
        ctx[connfd]->status = S_READING; // 初始只有READING状态, 当读取数据后变为WRITING?
        maxfd = connfd > maxfd ? connfd : maxfd;

        // set to non-blocking
        // nonblocking(connfd); // 这里看起来是不必要的, 因为fd变为可读后才会进入read_callback, 执行read() 不存在不可读就read()而陷入阻塞的情况
        FD_SET(connfd, &readfds);
    }
}

void close_callback(struct event * event)
{
    printf("close_callback\n");
    close(event->fd);
    free(ctx[event->fd]->rxbuf);
    free(ctx[event->fd]->txbuf);
    free(ctx[event->fd]);
    ctx[event->fd] = NULL;
    FD_CLR(event->fd, &readfds);
    for (int i = maxfd - 1; i >= 0; i--) {
        if (ctx[i]) {
            maxfd = i;
            return;
        }
    }
    maxfd = 0;
}

void handle_pending_events()
{
    // for (int i = 0; i < queue.len; i++) { // queue.len在遍历的过程中会变化, 所以不能使用这种遍历方式
    while (queue.len > 0) {
        struct event * event = dequeue(&queue);
        event->callback(event);
        free(event);
    }
}
